/*
 * @file     player_smart.c
 * @brief    This is an implementation of smart player using the minimax strategy.
 *
 * @author   Franco Riberi
 * @email    fgriberi AT gmail.com
 *
 *
 * @date March 09
 *
 * This file is part of board-games.
 *
 * board-games is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * board-games is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with board-games.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <stdio.h>
#include <string.h>
#include "player_smart.h"

inline void updateInternalBoard(const TTTBoard board, TTTBoard* const internalBoard)
{
    const unsigned int size = sizeof(int) * BOARD_SIZE * BOARD_SIZE;
    memcpy(internalBoard, board, size);
}

int checkWinnerInRow(/*const*/ TTTBoard* const board)
{
    unsigned int ret = INITIAL_POSITION;
    BoardIndex row = INITIAL_POSITION;
    while(row < BOARD_SIZE && ret == INITIAL_POSITION)
    {
        if (*board[row][INITIAL_POSITION] == *board[row][FIRST_POSITION] && 
            *board[row][FIRST_POSITION] == *board[row][SECOND_POSITION])
        {
            ret = *board[row][INITIAL_POSITION];
        }
        //else: ignored because ret still is zero
        ++row;
    }
    return ret;
}

int checkWinnerInCol(/*const*/ TTTBoard* const board)
{
    unsigned int ret = INITIAL_POSITION;
    BoardIndex col = INITIAL_POSITION;
    while(col < BOARD_SIZE && ret == INITIAL_POSITION)
    {
        if (*board[INITIAL_POSITION][col] == *board[FIRST_POSITION][col] && 
            *board[FIRST_POSITION][col] == *board[SECOND_POSITION][col])
        {
            ret = *board[INITIAL_POSITION][col];
        }
        //else: ignored because ret still is zero
        ++col;
    }
    return ret;
}

int checkWinnerInDiagonal(/*const*/ TTTBoard* const board)
{
    unsigned int ret = INITIAL_POSITION;
    if (*board[FIRST_POSITION][FIRST_POSITION] != BOARD_EMPTY)
    {
        if (*board[INITIAL_POSITION][INITIAL_POSITION] == *board[FIRST_POSITION][FIRST_POSITION] && 
            *board[FIRST_POSITION][FIRST_POSITION] == *board[SECOND_POSITION][SECOND_POSITION])
        {
            ret = *board[INITIAL_POSITION][INITIAL_POSITION];
        }
        if (*board[FIRST_POSITION][FIRST_POSITION] == *board[SECOND_POSITION][INITIAL_POSITION] && 
            *board[INITIAL_POSITION][SECOND_POSITION] == *board[FIRST_POSITION][FIRST_POSITION])
        {
            ret = *board[FIRST_POSITION][FIRST_POSITION];
        }
    }
    //else: ignored since it is impossible to have a winner in diagonal
    return ret;
}

int checkWinner(/*const*/ TTTBoard* const board)
{
    int winner = checkWinnerInRow(board);
    if (winner == NO_WINNER)
    {
        winner = checkWinnerInCol(board);
        if (winner == NO_WINNER)
        {
            winner = checkWinnerInDiagonal(board);
        }
        //else: ignored because there is a winner by column
    }
    //else: ignored because there is a winner by row
    return winner;    
}

int updateScore(int currentScore, int bestScore, const unsigned int depth, const BoardIndex row, const BoardIndex col)
{
    int newScore = bestScore;
    if (currentScore > bestScore)
    {
        if (depth != 0u)
        {
            _bestRow = row;
            _bestCol = col;
        }
        newScore = currentScore;;
    }
    //else : ignored because the bestScore is better
    return newScore;
}

int analyzeMovement(const BoardIndex row, const BoardIndex col, int currentScore, const int player, const unsigned int depth, int bestScore)
{
    currentScore = -minmax(-player, depth + 1u);
    bestScore = updateScore(currentScore, bestScore, depth, row, col);
    return bestScore;
}

int calculateBestMove(int currentScore, int bestScore, const int player, const unsigned int depth)
{
    int lastAttempt = 0u;
    BoardIndex row, col;
    for (row = 0u; row < BOARD_SIZE; ++row)
    {
        for(col = 0u; col < BOARD_SIZE; ++col)
        {
            if (_internalBoard[row][col] == BOARD_EMPTY) //is possible move
            {
                _internalBoard[row][col] = player; //try the move
                lastAttempt = _internalBoard[row][col];
                bestScore = analyzeMovement(row, col, currentScore, player, depth, bestScore);
                _internalBoard[row][col] = BOARD_EMPTY; //Reset board after try
            }
            //else: ignored because the place is busy
        }
    }
    return lastAttempt != BOARD_EMPTY ? bestScore : 0u;
}

int minmax(const int player, const unsigned int depth)
{
    int ret;
    int currentScore = checkWinner(&_internalBoard);
    if (currentScore != NO_WINNER)
    {
        ret = currentScore * player;
    }
    else
    {
        int bestScore = BEST_SCORE;
        ret = calculateBestMove(currentScore, bestScore, player, depth);
    }
    return ret;
}

void init_player(__attribute__((unused)) enum PLAYERS p)
{
    /* nothing to initialize here */
}

void play(const TTTBoard board, Coord* row, Coord* col)
{
    updateInternalBoard(board, &_internalBoard);
    minmax(INITIAL_PLAYER, INITIAL_DEPTH);
    *row = _bestRow;
    *col = _bestCol;
}
